import {PPUMemory} from './ppu-memory.js'
import {SpriteMemory} from './sprite-memory.js'

export class PPU{
    constructor(fc){
        this.fc = fc
        this.instance = null 
        this.vBuffer = new Array(256 * 240)
        this.vmemory = PPUMemory.getInstance()
        // 精灵RAM
        this.spriteMemory = SpriteMemory.getInstance()
        // 扫描线精灵缓存,32Byte数组，保存8个Sprite的当前扫描线行
        this.scanlineSpriteCache = new Array(32)
        for(let i = 0;i<32;i++){
            this.scanlineSpriteCache[i] = 0xFF
        }
        // PPU地址指针
        this.ppuAddress = 0x0000
        // PPU数据输入输出缓冲
        this.ppuDataBuffer = 0x00
        // CPU向PPU写数据时，要延迟一次获取完整地址，靠此标志位控制 
        this.firstWrite = true
        // PPU寄存器

        // $2000
        this.PPUCTRL_TABLE_NAME_LOW = 0
        this.PPUCTRL_TABLE_NAME_HI = 0
        this.PPUCTRL_VRAM_INCREMENT = 0
        this.PPUCTRL_SPRITE_NAMETABLE_ADD = 0
        this.PPUCTRL_BACKGROUND_NAMETABLE_ADD = 0
        // 在VBLANK时是否可以触发NMI，1为可以触发
        this.PPUCTRL_ENABLE_NMI = 0

        // $2001

        // $2002
        this.PPUSTATUS_SPRITE_OVERFLOW = 0
        // PPU是否处于VBLANK，在第241线第1点时置1，进入VBLANK，读取PPUSTATUS寄存器后置0
        this.PPUSTATUS_VBLANK_START = 0

        // $2003
        // $2004
        // $2005
        this.PPUSCROLL = 0x00
        // $2006
        // CPU需要写VRAM时用来指定地址的寄存器，需要写2次才能指定VRAM地址，写先写高字节（只有6bit有效），后写低字节
        this.PPUADDR = 0x00
        
        // $2007
        // 要写入显存的数据，PPU的指针会在这个寄存器读写后按照$2002的bit2来增加1或32
        // 通常读写会在VBLANK进行
        // 写这个寄存器应该同时改VRAM的内容 
        this.PPUDATA = 0x00

        this.initPalette()
        // 当前扫描线，初始化为0
        this.scanline = 0
        // 当前渲染的像素点位置，初始化为0
        this.curX = 0
        // 是否触发nim中断
        this.nim = false
        // 背景颜色，用来决定精灵透明
        this.transparentColor = 0x00
    }

    // 取当前像素点所在NameTable位置索引
    getCurrentPixelNameTableIndex(cruX,scanline){
        let ntIndex
        let ntX = parseInt(cruX / 8)
        let ntY = parseInt(scanline / 8)
        ntIndex = ntY * 32 + ntX
        return ntIndex
    }

    // 根据索引取NameTable中的值，得到在patternTable中的索引位置
    loadNameTableValueByIndex(ntIndex){
        let ntStartAddress = 0x2000
        let currentNtAddress = ntStartAddress + ntIndex
        let ptIndex = this.vmemory.load(currentNtAddress)
        return ptIndex
    }

    // 根据patternTable的索引和PPU控制寄存器的值，决定读取哪个位置的patternTable值
    // 以数组形式返回一个计算后的tile
    loadPatternTableByIndex(ptIndex,controlFlag){
        let ptStartAddress
        if(controlFlag == 0){
            ptStartAddress = 0x0000
        }
        else if(controlFlag == 1){
            ptStartAddress = 0x1000
        }
        // 图样表高8字节所在地址，因为图样表以16字节为一个分隔，所以把索引左移4位，相当于乘以16
        let first8ByteStartAddress = (ptIndex << 4) + ptStartAddress
        // 图样表低8字节所在地址，由高8位加8得到
        let second8ByteStartAddress = first8ByteStartAddress + 8
        let tileColorMartix = new Array(8)
        for(let i=0;i<tileColorMartix.length;i++){
            tileColorMartix[i] = new Array('0','0','0','0','0','0','0','0')
        }
        let lineByteLow 
        let lineByteHigh
        for(let i=0;i<8;i++){
            lineByteLow = this.vmemory.load(first8ByteStartAddress + i)
            lineByteHigh = this.vmemory.load(second8ByteStartAddress + i)
            for(let j=0;j<8;j++){
                let bitOfPixel =  (lineByteLow >> (7 - j) & 1 ) + (((lineByteHigh >> (7 - j) & 1 ) << 1) & 2)
                tileColorMartix[j][i] = bitOfPixel
            }
        }
        return tileColorMartix  
    }

    // 根据当前的x位置和扫描线位置，获取tile的颜色索引，再结合属性表取到实际像素
    // 返回调色盘索引低2bit
    getRealColor(curX,scanline,tileColorMartix){
        let tileX = curX % 8
        let tileY = scanline % 8
        let colorLow2Bit = tileColorMartix[tileX][tileY]
        return colorLow2Bit
    }

    // 获取当前像素所在tile的属性表中的调色盘偏移
    // 返回调色盘索引高2bit
    getAttributeTable(curX,scanline){
        let atIndex
        let atX = parseInt(curX / 32)
        let atY = parseInt(scanline / 32)
        // Fixed! 查找属性表时的步进值原来写成了16，应该按照一行的block数步进
        atIndex = atY * 8 + atX
        let atStartAddress = 0x23C0 
        let currentAtAddr = atStartAddress + atIndex
        // 取当前block的属性表，精灵需要加8的地址
        let atByte = this.vmemory.load(currentAtAddr)
        // 计算当前像所所在cell的总索引
        let cellX = parseInt(curX / 16)
        let cellY = parseInt(scanline / 16)
        // 计算当前cell位于block的那个位置
        let cellInBlockX = cellX % 2
        let cellInBlockY = cellY % 2
        let cellCount = ((cellInBlockY << 1) + cellInBlockX) & 3
        // console.log('cellCount:'+cellCount)
        // 从属性表中分离出调色盘地址高2bit，左上角cell用bit0、bit1，右上角用bit2、bit3，左下bit4、bit5，右下bit6、bit7
        let colorHi2Bit = (atByte >> (2*cellCount)) & 3 
        return colorHi2Bit
    } 

    getPaletteColor(colorLow2Bit,colorHi2Bit,type){
        let colorIndex = ((colorHi2Bit << 2) + colorLow2Bit) & 0xF
        let paletteStartAddress
        if(type == 'bg'){
            paletteStartAddress = 0x3F00
        }
        else{
            paletteStartAddress = 0x3F10
        }
        let realColorAddress = paletteStartAddress + colorIndex
        if(realColorAddress % 4 == 0){
            realColorAddress = paletteStartAddress
        }
        let colorNumber = this.vmemory.load(realColorAddress)
        return colorNumber
    }

    // 获取像素rgb值
    getColorByNumber(colorNumber){
        return this.palette[colorNumber]
    }

    setColorToBuffer(color,curX,scanline,buffer){
        let bufferIndex = scanline * 256 + curX
        buffer[bufferIndex] = 0xFF000000 | color
    }

    checkCurrentScanlineSprite(scanline){
        for(let i = 0;i<32;i++){
            this.scanlineSpriteCache[i] = 0xFF
        }
        this.PPUSTATUS_SPRITE_OVERFLOW = 0
        let currentScanlineSpriteCount = 0
        for(let i=0;i<64;i++){
            // 取y坐标Byte，OAM中保存的是y坐标减1，这里需要加上1
            let spriteY = this.spriteMemory.load(i * 4) + 1
            if(scanline >= spriteY && scanline < (spriteY + 8)){
                if(currentScanlineSpriteCount >= 7){
                    this.PPUSTATUS_SPRITE_OVERFLOW = 1
                    return
                }
                for(let j=0;j<4;j++){
                    this.scanlineSpriteCache[currentScanlineSpriteCount*4 + j] = this.spriteMemory.load(i * 4 + j)
                }
                currentScanlineSpriteCount++
                // console.log('精灵个数：'+currentScanlineSpriteCount) 
            }
        }
    }

    outputCurrentScanlineSprite(scanline,buffer){
        // console.log(this.scanlineSpriteCache)
        for(let i=0;i<8;i++){
            let spriteY = this.scanlineSpriteCache[i*4] + 1
            let spritePatternTableIndex = this.scanlineSpriteCache[i*4 + 1]
            let spriteAttribute = this.scanlineSpriteCache[i*4 + 2]
            let spriteX = this.scanlineSpriteCache[i*4 + 3]
            let tileColorMartix = this.loadSpritePatternTableByIndex(spritePatternTableIndex,this.PPUCTRL_SPRITE_NAMETABLE_ADD)
            
            if(scanline >= spriteY && scanline < (spriteY + 8)){
                // 更新每个sprite的横行8像素
                let filpHFlag = (spriteAttribute >> 6) & 1
                for(let j=0;j<8;j++){
                    let currentY = (scanline - spriteY) % 8 
                    let colorLow2Bit = this.getSpriteColorLow2Bit(j,currentY,tileColorMartix)
                    let colorHi2Bit = spriteAttribute & 3
                    let colorNumber = this.getPaletteColor(colorLow2Bit,colorHi2Bit,'sp')
                    let color = this.getColorByNumber(colorNumber)
                    if(filpHFlag == 0){
                        this.setSpriteColorToBuffer(color,spriteX + j,scanline,buffer)
                    }
                    else if(filpHFlag == 1){
                        this.setSpriteColorToBuffer(color,spriteX + (8 - j),scanline,buffer)
                    }  
                    
                }
            }
        }
    }

    // 根据patternTable的索引和PPU控制寄存器的值，决定读取哪个位置的patternTable值
    // 以数组形式返回一个计算后的tile
    loadSpritePatternTableByIndex(ptIndex,controlFlag){
        if(ptIndex == 162){

        }
        let ptStartAddress
        if(controlFlag == 0){
            ptStartAddress = 0x0000
        }
        else if(controlFlag == 1){
            ptStartAddress = 0x1000
        }
        // 图样表高8字节所在地址，因为图样表以16字节为一个分隔，所以把索引左移4位，相当于乘以16
        let first8ByteStartAddress = (ptIndex << 4) + ptStartAddress
        // 图样表低8字节所在地址，由高8位加8得到
        let second8ByteStartAddress = first8ByteStartAddress + 8
        let tileColorMartix = new Array(8)
        for(let i=0;i<tileColorMartix.length;i++){
            tileColorMartix[i] = new Array('0','0','0','0','0','0','0','0')
        }
        let lineByteLow 
        let lineByteHigh
        for(let i=0;i<8;i++){
            lineByteLow = this.vmemory.load(first8ByteStartAddress + i)
            lineByteHigh = this.vmemory.load(second8ByteStartAddress + i)
            for(let j=0;j<8;j++){
                let bitOfPixel =  (lineByteLow >> (7 - j) & 1 ) + (((lineByteHigh >> (7 - j) & 1 ) << 1) & 2)
                tileColorMartix[i][j] = bitOfPixel
            }
        }
        return tileColorMartix
    }

    getSpriteColorLow2Bit(tileX,tileY,tileColorMartix){
        let colorLow2Bit = tileColorMartix[tileY][tileX]
        return colorLow2Bit
    }

    setSpriteColorToBuffer(color,curX,scanline,buffer){
        if(color == this.transparentColor){
            return
        }
        let bufferIndex = scanline * 256 + curX
        buffer[bufferIndex] = 0xFF000000 | color
    }

    static getInstance(){
        if(!this.instance){
            this.instance = new PPU()
            return this.instance
        }
        else{
            return this.instance
        }
    }

    reset(framebuffer_u32){
        this.vBuffer = framebuffer_u32
    }

    // 按扫描线渲染
    executeByScanline(buffer,cpu){
        // console.log('-------------------pattern Table-------------------')
        // 输出patternTable
        var tempLine = ''
        let lineStart = 0x0000
        for(let i=0x0000;i<0x1000;i++){
            tempLine += this.toFormattedHex(this.vmemory.load(i),2) + ' '
            if(i - lineStart == 31){
                // console.log(tempLine)
                tempLine = ''
                lineStart = i + 1
            }
        }
        // 输出名称表
        // console.log('-------------------name Table------------------')
        var tempLineNameTable = ''
        let lineStartNameTable = 0x2000
        for(let i=0x2000;i<0x23C0;i++){
            tempLineNameTable += this.toFormattedHex(this.vmemory.load(i),2) + ' '
            if(i - lineStartNameTable == 31){
                // console.log(tempLineNameTable)
                tempLineNameTable = ''
                lineStartNameTable = i + 1
            }
        }
        // 每帧渲染到屏幕后通知CPU进行NMI
        this.PPUSTATUS_VBLANK_START = 1
        if(this.PPUCTRL_ENABLE_NMI == 1){
            // 输出精灵内存
            // console.log(this.spriteMemory.mem)
            // 获取背景色
            this.transparentColor = this.palette[this.vmemory.load(0x3F00)]
            // 将名称表输出到屏幕缓冲区
            for(let scanline=0;scanline<240;scanline++){
                for(let curX=0;curX<256;curX++){
                    let ntIndex = this.getCurrentPixelNameTableIndex(curX,scanline)
                    let ptIndex = this.loadNameTableValueByIndex(ntIndex)
                    let tileColorMartix = this.loadPatternTableByIndex(ptIndex,this.PPUCTRL_BACKGROUND_NAMETABLE_ADD)
                    let colorLow2Bit = this.getRealColor(curX,scanline,tileColorMartix)
                    let colorHi2Bit = this.getAttributeTable(curX,scanline)
                    let colorNumber = this.getPaletteColor(colorLow2Bit,colorHi2Bit,'bg')
                    let color = this.getColorByNumber(colorNumber)
                    this.setColorToBuffer(color,curX,scanline,buffer)
                }
                // 获取当前扫描线的Sprite
                this.checkCurrentScanlineSprite(scanline)
                // 遍历当前扫描线的Sprite，决定颜色和是否需要覆盖背景色
                this.outputCurrentScanlineSprite(scanline,buffer)
            }
            cpu.INTERRUPT_NMI = 1
        }
            

        // // 预备阶段
        // // console.log(this.scanline+';'+this.pixelX)
        // if(this.scanline == -1){
        //     cpu.INTERRUPT_NMI = 0
        // }
        // // 正常渲染，需要计算出1个像素的颜色
        // if(this.scanline >= 0 && this.scanline < 240){
        //     // 只有0-255共256个像素点有颜色
        //     if(this.pixelX < 256)
        //     buffer[this.scanline * 256 + this.pixelX] = 0xFF000000 | this.toFormattedHex(parseInt(Math.random()*256*256*256).toString(16),6)

        //     // buffer[curCycle] = 0xFF000000 | this.toFormattedHex(parseInt(Math.random()*256*256*256).toString(16),6)
        // }
        // // TO FIX NMI中断，其实不应在这触发
        // if(this.scanline == 240){
        //     cpu.INTERRUPT_NMI = 1
        // }
        // // 走一个像素点
        // this.pixelX++
        // // 走完一行像素，增加扫描线行数到下一行
        // if(this.pixelX > 341){
        //     this.scanline++
        //     this.pixelX = 0
        // }
        // // // console.log('ppu execute')

    }

    // PPU每次执行会决定1个像素点，然后写入到需要前台输出的缓冲中
    // 实际的PPU是每次执行时直接向屏幕输出1个像素，但模拟时可以等到1帧的像素全部确定后，在用绘图API绘制到屏幕
    // 暂时先实现帧同步，一次性输出所有像素点
    execute(buffer,cpu){
        // console.log('-------------------pattern Table-------------------')
        // 输出patternTable
        var tempLine = ''
        let lineStart = 0x0000
        for(let i=0x0000;i<0x1000;i++){
            tempLine += this.toFormattedHex(this.vmemory.load(i),2) + ' '
            if(i - lineStart == 31){
                // console.log(tempLine)
                tempLine = ''
                lineStart = i + 1
            }
        }
        // 输出名称表
        // console.log('-------------------name Table------------------')
        var tempLineNameTable = ''
        let lineStartNameTable = 0x2000
        for(let i=0x2000;i<0x23C0;i++){
            tempLineNameTable += this.toFormattedHex(this.vmemory.load(i),2) + ' '
            if(i - lineStartNameTable == 31){
                // console.log(tempLineNameTable)
                tempLineNameTable = ''
                lineStartNameTable = i + 1
            }
        }
        
                
        // }

        // console.log('in PPU execute')
        // 输出CPU内存位置0x2000的PPU寄存器值
        // 输出256*240 = 61440个像素点
        // for(let i=0;i<61440;i++){
        //     // 向屏幕缓冲区填充随机像素
        //     // buffer[curCycle] = this.toFormattedHex(parseInt(Math.random()*256).toString(16),2)
        //     // // console.log(buffer[curCycle])
        //     buffer[i] = 0xFF000000 | this.toFormattedHex(parseInt(Math.random()*256*256*256).toString(16),6)
        // }
        // 按照nameTable中的数据输出背景像素
        // 从显存的0x2000处开始取数据，取出第一屏的960个tile
        // for(let i=0x2000;i<0x23C0;i++){
        //     // 解析每一个tile，获取它指向的PatternTabel的代码
        //     let tileCode = this.vmemory.load(i)
        //     // console.log('tileCode:'+tileCode)
        //     // 有了tileCode，能直到图样的形状位置位于显存的0x0000+tileCode地址处，分为前后各8Byte
        //     // 所以应该一次性读取16Byte，然后拆分成前8Byte和后8Byte
        //     // 此时已经通过PatternTable获取到了需要渲染的图案形状，只需再通过这两个点阵加上属性表来确定实际的颜色，然后逐个输出就可以了
        //     // 先不取颜色，尝试只把点阵输出，取前8Byte中64个像素点对应位置的bit值和后8Byte中64个像素点的bit值相加，得到0-3的值，然后设置4中默认颜色来输出
        //     let patternFrontLine1 = this.vmemory.load(tileCode)
        //     let patternBackLine1 = this.vmemory.load(tileCode + 8)
        //     // // console.log('f:'+patternFrontLine1 +';b:'+patternBackLine1)
        //     for(let j=0;j<7;j++){
        //         let bit0 = patternFrontLine1 >> j & 1
        //         let bit1 = patternBackLine1 >> j & 1
        //         let bit = bit0 + bit1
        //         if(bit == 0){
        //             buffer[j] = 0xFF000000 | 0xFFFFFF
        //         }
        //         if(bit == 1){
        //             buffer[j] = 0xFF000000 | 0x000000
        //         }
        //         if(bit == 2){
        //             buffer[j] = 0xFF000000 | 0x00FFFF
        //         }
        //         if(bit == 3){
        //             buffer[j] = 0xFF000000 | 0x00FF00
        //         }
        //     }
        // }
        // 每帧渲染到屏幕后通知CPU进行NMI
        this.PPUSTATUS_VBLANK_START = 1
        if(this.PPUCTRL_ENABLE_NMI == 1){
            // 将名称表输出到屏幕缓冲区
            for(let i=0x2000;i<0x23C0;i++){
                // 获取到一个tile
                let patternCode = this.vmemory.load(i)
                let myTile = new Tile(patternCode)
                myTile.getPatternAddress()
                myTile.fillPixelColorMartix()
                // myTile.showPixelColorMartix()
                let screenRow = parseInt((i - 0x2000) / 32)
                let screenColumn = (i - 0x2000) % 32
                let showTilePos = false
                // if(i - 0x2000 == 0x42){
                //     console.log('输出第'+screenRow+'行，第'+screenColumn+'列的tile')
                //     showTilePos = true
                // }
                myTile.setTileToBuffer(screenRow,screenColumn,buffer,showTilePos)
                // if(i - 0x2000 == 0x42){
                //     break
                // }
                // let tileLine = (i - 0x2000) / 32
                // let tileRow = (i - 0x2000) % 32
                // // 在patternTable中找到这个tile的连续
                // // 如果tile的16进制数为TT，则它在patternTable中的位置是0x0TT0到0x0TTF，总共16字节，2个平面
                // // 或者是0x1TT0到0x1TTF，总共16字节，2个平面，这取决于PPU寄存器中的状态
                // // 先默认取0x0TT0的位置，尝试输出
                // let temp = tile << 4 & 0x1FF0
                // for(let j=0;j<8;j++){
                //     let frontByte = this.vmemory.load(j + temp)
                //     let backByte = this.vmemory.load(j + temp + 8)
                //     for(let k=0;k<8;k++){
                //         let frontBit = frontByte >> (7 - k) & 1
                //         let backBit = backByte >> (7 - k) & 1
                //         let resultIndex = frontBit + backBit
                //         let selectColor
                //         switch (resultIndex){
                //             case 0:
                //                 selectColor = 0x000000
                //                 break
                //             case 1:
                //                 selectColor = 0xFF0000
                //                 break
                //             case 2:
                //                 selectColor = 0x00FF00
                //                 break
                //             case 3:
                //                 selectColor = 0x0000FF
                //                 break
                //         }

                //         // console.log('index :'+ (i - 0x2000) * 64 + j*8 + k)
                //         // console.log(0xFF000000 | selectColor)
                //         buffer[tileLine * 256 * 8 + j * 256 + tileRow * 8 +  k] = 0xFF000000 | selectColor
                //         // buffer[parseInt(i - 0x2000) * 64 + j*8 + k] = 0xFF000000 | this.toFormattedHex(parseInt(Math.random()*256*256*256).toString(16),6)
                //     }
                // }
            }
            // TODO 输出精灵
            // 读取精灵内存，拆分64个精灵
            // 循环，从最后一个开始倒序渲染
            //     读取x坐标，读取y坐标+1
            //     读取tile代码
            //     将每个tile的像素写到对应的buffer位置，需要转换
            
            cpu.INTERRUPT_NMI = 1
        }
            

        // // 预备阶段
        // // console.log(this.scanline+';'+this.pixelX)
        // if(this.scanline == -1){
        //     cpu.INTERRUPT_NMI = 0
        // }
        // // 正常渲染，需要计算出1个像素的颜色
        // if(this.scanline >= 0 && this.scanline < 240){
        //     // 只有0-255共256个像素点有颜色
        //     if(this.pixelX < 256)
        //     buffer[this.scanline * 256 + this.pixelX] = 0xFF000000 | this.toFormattedHex(parseInt(Math.random()*256*256*256).toString(16),6)

        //     // buffer[curCycle] = 0xFF000000 | this.toFormattedHex(parseInt(Math.random()*256*256*256).toString(16),6)
        // }
        // // TO FIX NMI中断，其实不应在这触发
        // if(this.scanline == 240){
        //     cpu.INTERRUPT_NMI = 1
        // }
        // // 走一个像素点
        // this.pixelX++
        // // 走完一行像素，增加扫描线行数到下一行
        // if(this.pixelX > 341){
        //     this.scanline++
        //     this.pixelX = 0
        // }
        // // // console.log('ppu execute')

    }



    setBuffer(buffer){
        
    }


    // 按照CPU执行一条指令需要的周期数*3后来执行PPU，cycle可能会是12或16
    executeByCycle(cycle,cpu,buffer){
        // 循环cycle个周期
        for( ; cycle>0; cycle--){
            // 渲染可见扫描线0-239
            if(this.scanline >=0 && this.scanline <= 239){
                if(this.curX >=0 && this.curX < 256){
                    // 将当前像素写入缓冲区
                    // 硬件上来说应该是通过提前缓冲好的移位寄存器输出，寄存器中的内容应该是在261线提前获取的
                    // 模拟时由于宿主机速度快，可以尝试直接输出
                    let ntIndex = this.getCurrentPixelNameTableIndex(this.curX,this.scanline)
                    let ptIndex = this.loadNameTableValueByIndex(ntIndex)
                    let tileColorMartix = this.loadPatternTableByIndex(ptIndex,this.PPUCTRL_BACKGROUND_NAMETABLE_ADD)
                    let colorLow2Bit = this.getRealColor(this.curX,this.scanline,tileColorMartix)
                    let colorHi2Bit = this.getAttributeTable(this.curX,this.scanline)
                    let colorNumber = this.getPaletteColor(colorLow2Bit,colorHi2Bit,'bg')
                    let color = this.getColorByNumber(colorNumber)
                    this.setColorToBuffer(color,this.curX,this.scanline,buffer)
                    // let bufferIndex = this.scanline * 256 + this.curX
                    // buffer[bufferIndex] = 0xFF000000 | 0XAA3242
                }
                if(this.curX == 256){
                    this.checkCurrentScanlineSprite(this.scanline)
                    this.outputCurrentScanlineSprite(this.scanline,buffer)
                }  
            }
            if(this.scanline == 240){
                // PostRender线，此时PPU不做渲染，但V-BLANK标志还没有设置
            }
            if(this.scanline == 241){
                if(this.curX == 1){
                    // 241线像素1开始V-BLANK
                    this.PPUSTATUS_VBLANK_START = 1
                    // 重要！必须判断PPU的寄存器来确定能否触发CPU中断，否则开始的时候CPU会直接中断
                    // 导致内存中数据不正确
                    if(this.PPUCTRL_ENABLE_NMI == 1){
                        cpu.INTERRUPT_NMI = 1
                    }
                }
            }
            if(this.scanline >= 242 && this.scanline <= 260){
                // V-BLANK中，只计算周期，PPU不访问显存
            }
            if(this.scanline == 261){
                // 虚拟扫描线，硬件上用来提前获取可见扫描线的内容放入移位寄存器
                if(this.curX == 1){
                    // 261线像素1清除V-BLANK标志
                    cpu.INTERRUPT_NMI = 0
                }
            }
            if(this.curX == 340){
                this.curX = 0
                if(this.scanline == 261){
                    this.scanline = 0
                }
                else{
                    this.scanline++
                }
            }
            this.curX++
        }
        
    }

    writePPUCTRL(data){
        this.PPUCTRL_TABLE_NAME_LOW = data & 1
        this.PPUCTRL_TABLE_NAME_HI = data >> 1 & 1
        this.PPUCTRL_VRAM_INCREMENT = data >> 2 & 1
        this.PPUCTRL_SPRITE_NAMETABLE_ADD = data >> 3 & 1
        this.PPUCTRL_BACKGROUND_NAMETABLE_ADD = data >> 4 & 1
        // 在VBLANK时是否可以触发NMI，1为可以触发
        this.PPUCTRL_ENABLE_NMI = data >> 7 & 1
    }

    readPPUSTATUS(){
        // console.log('reading PPUSTATUS')
        let status
        if(this.PPUSTATUS_VBLANK_START == 1){
            
        }
        status = (this.PPUSTATUS_VBLANK_START << 7) | 0x00 
        this.PPUSTATUS_VBLANK_START = 0
        if(status == 0x80){
            // console.log('PPU status:'+status.toString(16))
        }
        return status
    }

    // 第一次写高8位，第二次写低8位，然后只取14位
    writeVRAMAddress(data){
        if(this.firstWrite){
            // console.log('fistWriteAdd high byte:'+data.toString(16))
            this.ppuAddress = (data << 8) & 0x3F00
            // console.log('after first write :'+ this.ppuAddress.toString(16))
        }
        else{
            // console.log('second write :'+ data.toString(16))
            // console.log('last address is:'+ this.ppuAddress)
            this.ppuAddress = this.ppuAddress | (data & 0x00FF)
            // console.log('after second write :'+ this.ppuAddress.toString(16))
        }
        this.firstWrite = !this.firstWrite
    }

    readData(){
        let data = this.vmemory[this.ppuAddress]
        this.ppuAddress += this.PPUCTRL_VRAM_INCREMENT == 0 ? 1 : 32
        return data
    }

    writeVRAM(data){
        // console.log('CPU is writing VRAM,add:'+this.ppuAddress.toString(16)+';data:'+data.toString
        // (16))
        this.vmemory.write(this.ppuAddress,data)
        this.ppuAddress += this.PPUCTRL_VRAM_INCREMENT == 0 ? 1 : 32
    }

    toFormattedHex(num,length){
        var len = num.toString(16).length
        var outStr = num.toString(16).toUpperCase()
        while(len < length) {
            outStr = '0' + outStr
            len++
        }
        return outStr
    }

    initPalette(){
        //64色调色盘RGB表示
        this.palette = 
        [0x525252, 0xB40000, 0xA00000, 0xB1003D, 0x740069, 0x00005B, 0x00005F, 0x001840,
         0x002F10, 0x084A08, 0x006700, 0x124200, 0x6D2800, 0x000000, 0x000000, 0x000000,
         0xC4D5E7, 0xFF4000, 0xDC0E22, 0xFF476B, 0xD7009F, 0x680AD7, 0x0019BC, 0x0054B1,
         0x006A5B, 0x008C03, 0x00AB00, 0x2C8800, 0xA47200, 0x000000, 0x000000, 0x000000,
         0xF8F8F8, 0xFFAB3C, 0xFF7981, 0xFF5BC5, 0xFF48F2, 0xDF49FF, 0x476DFF, 0x00B4F7, 
         0x00E0FF, 0x00E375, 0x03F42B, 0x78B82E, 0xE5E218, 0x787878, 0x000000, 0x000000, 
         0xFFFFFF, 0xFFF2BE, 0xF8B8B8, 0xF8B8D8, 0xFFB6FF, 0xFFC3FF, 0xC7D1FF, 0x9ADAFF, 
         0x88EDF8, 0x83FFDD, 0xB8F8B8, 0xF5F8AC, 0xFFFFB0, 0xF8D8F8, 0x000000, 0x000000]
    }

}

class Tile{
    constructor(patternCode){
        this.vmemory = PPUMemory.getInstance()
        this.patternCode = patternCode
        this.pixelColorMartix = new Array(8)
        for(let i=0;i<this.pixelColorMartix.length;i++){
            this.pixelColorMartix[i] = new Array('0','0','0','0','0','0','0','0')
        }
    }
    getPatternAddress(){
        this.first8ByteStartAddress = (this.patternCode << 4) & 0x0FF0
        this.second8ByteStartAddress = this.first8ByteStartAddress + 8
        // console.log('first 8Byte:'+first8ByteStartAddress.toString(16)+';second 8Byte:'+second8ByteStartAddress.toString(16))
    }
    fillPixelColorMartix(){
        let lineByteLow 
        let lineByteHigh
        for(let i=0;i<8;i++){
            lineByteLow = this.vmemory.load(this.first8ByteStartAddress + i)
            lineByteHigh = this.vmemory.load(this.second8ByteStartAddress + i)
            for(let j=0;j<8;j++){
                let bitOfPixel =  (lineByteLow >> (7 - j) & 1 ) + ((lineByteHigh >> (7 - j) & 1 ) << 1 & 2)
                this.pixelColorMartix[i][j] = bitOfPixel
            }
        }
    }
    showPixelColorMartix(){
        for(let i=0;i<8;i++){
            let tempLine = ''
            for(let j=0;j<8;j++){
                tempLine += this.pixelColorMartix[i][j] + ' '
            }
            // console.log(tempLine)
        }
        // console.log('----------------------------')
    }
    setTileToBuffer(screenRow,screenColumn,buffer,showTilePos){
        let pixel
        let selectColor
        let indexOfBuffer
        for(let i=0;i<8;i++){
            for(let j=0;j<8;j++){
                pixel = this.pixelColorMartix[i][j]
                indexOfBuffer = screenRow * 256 * 8 + i * 256 + screenColumn * 8 +  j
                if(showTilePos){
                    // console.log('第'+screenRow+'行，第'+screenColumn+'列的tile，第'+i+'行，第'+j+'列的pixel，buffer位置为'+indexOfBuffer)
                }
                switch (pixel){
                    case 0:
                        selectColor = 0x000000
                        break
                    case 1:
                        selectColor = 0xFF0000
                        break
                    case 2:
                        selectColor = 0x00FF00
                        break
                    case 3:
                        selectColor = 0x0000FF
                        break
                }
                buffer[indexOfBuffer] = 0xFF000000 | selectColor
            }
        }
    }
    showName(){
        // console.log(this.patternCode.toString(16).toUpperCase())
    }
    
}